// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.ComponentModel.DataAnnotations.Schema;

namespace SnapObjects.Data
{
    internal class UpdatableModel<TModel>
        : UpdatableModel, ISaveAction
    {
        public UpdatableModel(
            IModelMeta modelMeta,
            ChangeTrackingStrategy changeTrackingStrategy,
            UpdateWhereStrategy updateWhereStrategy,
            IStatefulModel statefulModel,
            bool checkLatestState,
            Action<ISaveContext> beforeSaveAction = null,
            Action<ISaveContext> afterSaveAction = null,
            IEmbeddedStateTracker embeddedStateTracker = null)
            : base(modelMeta, changeTrackingStrategy, updateWhereStrategy,
                                    statefulModel, checkLatestState, embeddedStateTracker)
        {
            this.BeforeSaveAction = beforeSaveAction;
            this.AfterSaveAction = afterSaveAction;
        }

        public Action<ISaveContext> BeforeSaveAction { get; }

        public Action<ISaveContext> AfterSaveAction { get; }

        public Type TargetType => typeof(TModel);

        public bool HasBeforeAction => this.BeforeSaveAction != null;

        public bool HasAfterAction => this.AfterSaveAction != null;
    }

    internal class UpdatableModel : IUpdatableModel
    {
        private readonly IStatefulModel _statefulModel;
        private readonly ChangeTrackingStrategy _changeTrackingStrategy;

        public UpdatableModel(
            IModelMeta modelMeta,
            ChangeTrackingStrategy changeTrackingStrategy,
            UpdateWhereStrategy updateWhereStrategy,
            IStatefulModel statefulModel,
            bool checkLatestState,
            IEmbeddedStateTracker embeddedStateTracker = null)
        {
            this.ModelMeta = modelMeta;
            this.ModelState = statefulModel.ModelState;
            _changeTrackingStrategy = changeTrackingStrategy;
            this.UpdateWhereStrategy = updateWhereStrategy;
            _statefulModel = statefulModel;
            this.EmbeddedStateTracker = embeddedStateTracker;

            if (checkLatestState)
            {
                this.ModelState = statefulModel.LatestModelState;
            }

            //For ModelMapper master/detail track
            this.IgnoreSave = this.ModelState == ModelState.NotModified ||
                            this.ModelState == ModelState.New ? true : false;
        }

        public UpdateType UpdateType => UpdateType.ModelUpdate;

        public IModelMeta ModelMeta { get; }

        public ModelState ModelState { get; }

        public bool IgnoreSave { get; }

        public UpdateWhereStrategy UpdateWhereStrategy { get; }

        public IEmbeddedStateTracker EmbeddedStateTracker { get; }

        public bool UppercaseIdentifier { get; protected set; }

        public object GetCurrentValue(IPropertyMeta property)
        {
            return _statefulModel.GetCurrentValue(property.Name);
        }

        public object GetOriginalValue(IPropertyMeta property)
        {
            return _statefulModel.GetOriginalValue(property.Name);
        }

        public bool IsModified(IUpdatableProperty property)
        {
            return _statefulModel.IsValueModified(property);
        }

        public void SetIdentityValue(IUpdatableProperty property, object value)
        {
            var isDatabaseGenerated = property.IsIdentity || 
                property.IsTimestamp ||
                (property.SaveStrategy == SaveStrategy.ReadAfterSave);

            if (!isDatabaseGenerated && property is IPocoProperty p)
            {
                isDatabaseGenerated = p.Attribute.SqlDefaultValueDefined ||
                    (p.Attribute.DatabaseGeneratedDefined &&
                       p.Attribute.DatabaseGenerated.DatabaseGeneratedOption != DatabaseGeneratedOption.None);
            }

            if (!isDatabaseGenerated)
            {
                throw new InvalidOperationException("The property is not an database generated property.");
            }

            if (property.HasConversion)
            {
                value = property.ValueConverter.ConvertToProperty(value);
            }

            _statefulModel.SetIdentityValue(property.Name, value);
        }

        public void PerformSetValueOperations()
        {
            _statefulModel.PerformSetValueOperations(this.EmbeddedStateTracker);
        }

        internal void SetPropertyValue(IPropertyMeta property, object value)
        {
            _statefulModel.SetCurrentValue(property.Index, value);
        }

        internal object Current => _statefulModel.Current;
    }
}
